#ifndef MESH_H
#define MESH_H

#include <assimp/cimport.h>
#include <assimp/scene.h>
#include <assimp/postprocess.h>
#include <assimp/Importer.hpp>

#include <glm/glm.hpp>
#include <glm/gtc/matrix_transform.hpp>

#define GLM_ENABLE_EXPERIMENTAL
#include <glm/gtx/quaternion.hpp>

#include <vector>
#include "Renderer/texture.h"

#include "Scene/animation.h"
#include "Scene/animator.h"
#include "Scene/animdata.h"
#include "Scene/assimp_glm_helpers.h"
#include "Scene/bone.h"
#include "Core/utils.h"
#include "Renderer/vertexArray.h"

namespace CC
{

#define MAX_BONE_INFLUENCE (4)

    class Animator;
    class Animation;

    struct Vertex
    {
        glm::vec3 Position;
        glm::vec3 Normal;
        glm::vec2 TexCoords;
        glm::vec3 Tangent;
        glm::vec3 Bitangent;
        // bone indexes which will influence this vertex
        int m_BoneIDs[MAX_BONE_INFLUENCE];
        // weights from each bone
        float m_Weights[MAX_BONE_INFLUENCE];

        std::string Serialize() {
            std::string data;
            //CC_CORE_INFO("Vertex Serialize %f %f %f\n", Position[0], Position[1], Position[2]);
            data += Tool::Vec32Str(Position) + Tool::Vec32Str(Normal) + Tool::Vec22Str(TexCoords)\
                + Tool::Vec32Str(Tangent) + Tool::Vec32Str(Bitangent)\
                + Tool::Int42Str(m_BoneIDs) + Tool::Float42Str(m_Weights);
            return data;
        }

        Vertex Parse(std::string data) {
            Vertex v;

            std::stringstream ss(data);
            std::string item;

            int i = 0;
            while (std::getline(ss, item, ',')) {
                if (!item.empty()) {

                    //CC_CORE_INFO("item= %d : %s === %s\n", i, item.c_str(), data.c_str());
                    if (i == 0) {
                        v.Position[0] = Tool::Str2Float(item);
                    }
                    else if (i == 1) {
                        v.Position[1] = Tool::Str2Float(item);
                    }
                    else if (i == 2) {
                        v.Position[2] = Tool::Str2Float(item);
                    }

                    else if (i == 3) {
                        v.Normal[0] = Tool::Str2Float(item);
                    }
                    else if (i == 4) {
                        v.Normal[1] = Tool::Str2Float(item);
                    }
                    else if (i == 5) {
                        v.Normal[2] = Tool::Str2Float(item);
                    }

                    else if (i == 6) {
                        v.TexCoords[0] = Tool::Str2Float(item);
                    }
                    else if (i == 7) {
                        v.TexCoords[1] = Tool::Str2Float(item);
                    }

                    else if (i == 8) {
                        v.Tangent[0] = Tool::Str2Float(item);
                    }
                    else if (i == 9) {
                        v.Tangent[1] = Tool::Str2Float(item);
                    }
                    else if (i == 10) {
                        v.Tangent[2] = Tool::Str2Float(item);
                    }

                    else if (i == 11) {
                        v.Bitangent[0] = Tool::Str2Float(item);
                    }
                    else if (i == 12) {
                        v.Bitangent[1] = Tool::Str2Float(item);
                    }
                    else if (i == 13) {
                        v.Bitangent[2] = Tool::Str2Float(item);
                    }

                    else if (i == 14) {
                        v.m_BoneIDs[0] = Tool::Str2Int(item);
                    }
                    else if (i == 15) {
                        v.m_BoneIDs[1] = Tool::Str2Int(item);
                    }
                    else if (i == 16) {
                        v.m_BoneIDs[2] = Tool::Str2Int(item);
                    }
                    else if (i == 17) {
                        v.m_BoneIDs[3] = Tool::Str2Int(item);
                    }

                    else if (i == 18) {
                        v.m_Weights[0] = Tool::Str2Float(item);
                    }
                    else if (i == 19) {
                        v.m_Weights[1] = Tool::Str2Float(item);
                    }
                    else if (i == 20) {
                        v.m_Weights[2] = Tool::Str2Float(item);
                    }
                    else if (i == 21) {
                        v.m_Weights[3] = Tool::Str2Float(item);
                    }
                    i++;
                }
            }
            CC_ASSERT(i <= 22, "ASSERT: Vertex Parse i = %d %s\n", i, data.c_str());
            return v;
        }
    };

    struct MaterialMesh
    {
        glm::vec4 Ambient;
        glm::vec4 Diffuse;
        glm::vec4 Specular;
        float Shininess;

        MaterialMesh() = default;
        MaterialMesh(const MaterialMesh &) = default;

        MaterialMesh(glm::vec4 a, glm::vec4 d, glm::vec4 s, float sh)
            : Ambient(a), Diffuse(d), Specular(s), Shininess(sh)
        {
        }

        MaterialMesh(glm::vec4 d)
            : Ambient(glm::vec4(0.0)), Diffuse(d), Specular(glm::vec4(0.0)), Shininess(0.0)
        {
        }
    };

    //extern AnimationInfo g_AnimationInfo;

    typedef struct _StdImgInfo
    {
        std::string file;
        std::string typeName;
        unsigned char * imgData;
        int width;
        int height;
        int channels;
        TextureType textureType;
    }StdImgInfo;


    typedef struct
    {
        glm::vec3 Position;
        glm::vec4 Color;
        glm::vec3 Normal;
        glm::vec2 TexCoord;
        glm::vec3 Tangent;
        glm::vec3 Bitangent;
        glm::ivec4 BoneIDs;
        glm::vec4 Weights;

        float TexIndex;
        float TilingFactor;

        // Editor-only
        int EntityID;
    }TriangleVertex;

    class Mesh
    {
    public:
        std::string Name;
        std::vector<Vertex> Vertices;
        std::vector<unsigned int> Indices;
        std::vector<Texture2D *> Textures;

        std::vector<StdImgInfo> TexturesData;//sync

        MaterialMesh ColorMaterial;
        bool HasColorMaterial;

        bool Init=false;
        bool MultiInstance=false;

        VertexArray *TriangleVertexArray=NULL;
        TriangleVertex* TriangleVertexBufferBase = NULL;
        uint32_t TriangleIndexCount = 0;

        Mesh() {}

        Mesh(std::string name, std::vector<Vertex> vertices,
             std::vector<unsigned int> indices, std::vector<Texture2D *> textures);

        Mesh(std::string name, std::vector<Vertex> vertices,
            std::vector<unsigned int> indices, std::vector<StdImgInfo> texturedatas);

        Mesh(std::string name, std::vector<Vertex> vertices,
             std::vector<unsigned int> indices, MaterialMesh colorMaterial);

        ~Mesh();

        void SetupMesh();
        void SetupMeshInstance(const Mesh* mesh);

        std::string SerializeVertex();
        std::string SerializeIndices();
        std::string SerializeTextures();

        void ParseVertex(std::string data);
        void ParseIndices(std::string data);
        void ParseTextures(std::string data);

    };

    class MeshHelper
    {
    public:
        std::vector<Mesh> MeshCreate(const std::string &path, const std::string &name, AnimationInfo &a, bool sync = false, TextureType textureType = TextureType_ClampToEdge);

        void LoadModelFromFile(const std::string &path, bool sync = false, TextureType textureType = TextureType_ClampToEdge);
        void ProcessNode(aiNode *node, const aiScene *scene, bool sync = false, TextureType textureType = TextureType_ClampToEdge);
        Mesh ProcessMesh(aiMesh *mesh, const aiScene *scene, bool sync = false, TextureType textureType = TextureType_ClampToEdge);
        void SetVertexBoneDataToDefault(Vertex &vertex);

        std::vector<Texture2D *> LoadMaterialTextures(aiMaterial *mat, aiTextureType type, std::string typeName, TextureType textureType = TextureType_ClampToEdge);
        std::vector<StdImgInfo> LoadMaterialTexturesSync(aiMaterial *mat, aiTextureType type, std::string typeName, TextureType textureType = TextureType_ClampToEdge);
        MaterialMesh LoadMaterialWithoutTextures(aiMaterial *mat);
        void ExtractBoneWeightForVertices(std::vector<Vertex> &vertices, aiMesh *mesh, const aiScene *scene);
        void SetVertexBoneData(Vertex &vertex, int boneID, float weight);

        AnimationInfo g_AnimationInfo;
        std::vector<Mesh> m_Meshes;
    };

   
    class Model
    {
    public:
        std::string FilePath;
        std::string Name;
        std::vector<Mesh> Meshes;
        AnimationInfo AnimationInfo;
    };
}

#endif
